﻿#region Includes
using System;
using System.Collections.Generic;
using GingerGL;
using GingerGL.Math;
using OpenTK;
#endregion

namespace GingerGL.Graphics
{
    //------------------------------------------------------------//
    /// <summary>
    /// Octree Leaf
    /// </summary>
    public class OctreeLeaf
    {
        //--------------------------------------------------------//
        #region Variables
        /// <summary>
        /// Maximum Leaf Depth
        /// </summary>
        protected const int MaxDepth = 40;
        /// <summary>
        /// Do a Fast Calculation
        /// </summary>
        protected const bool DoFastOut = true;
        /// <summary>
        /// Maximum Children
        /// </summary>
        protected const int MaxObjects = 8;
        #endregion
        //--------------------------------------------------------//
        #region Properties
        /// <summary>
        /// Objects in Leaf
        /// </summary>
        public List<OctreeObject> ContainedObjects { get; set; }
        /// <summary>
        /// List of Children
        /// </summary>
        public List<OctreeLeaf> ChildLeaves { get; protected set; }
        /// <summary>
        /// Container Box
        /// </summary>
        protected BoundingBox ContainerBox { get; set; }
        #endregion
        //--------------------------------------------------------//
        #region Constructors
        /// <summary>
        /// Initailize a new insance of object
        /// </summary>
        public OctreeLeaf(BoundingBox containerBox) { this.ContainerBox = containerBox; }
        #endregion
        //--------------------------------------------------------//
        #region Methods
        /// <summary>
        /// Split Oct
        /// </summary>
        protected void Split()
        {
            if (this.ChildLeaves != null) return;

            Vector3 half = ContainerBox.Max - ContainerBox.Min;
            half *= 0.5f;
            Vector3 halfx = new Vector3(half.X, 0, 0);
            Vector3 halfy = new Vector3(0, half.Y, 0);
            Vector3 halfz = new Vector3(0, 0, half.Z);

            ChildLeaves = new List<OctreeLeaf>();

            this.ChildLeaves.Add(new OctreeLeaf(new BoundingBox(ContainerBox.Min, ContainerBox.Min + half)));
            this.ChildLeaves.Add(new OctreeLeaf(new BoundingBox(ContainerBox.Min + halfx, ContainerBox.Max - half + halfx)));
            this.ChildLeaves.Add(new OctreeLeaf(new BoundingBox(ContainerBox.Min + halfz, ContainerBox.Min + half + halfz)));
            this.ChildLeaves.Add(new OctreeLeaf(new BoundingBox(ContainerBox.Min + halfx + halfz, ContainerBox.Max - halfy)));
            this.ChildLeaves.Add(new OctreeLeaf(new BoundingBox(ContainerBox.Min + halfy, ContainerBox.Max - halfx - halfz)));
            this.ChildLeaves.Add(new OctreeLeaf(new BoundingBox(ContainerBox.Min + halfy + halfx, ContainerBox.Max - halfz)));
            this.ChildLeaves.Add(new OctreeLeaf(new BoundingBox(ContainerBox.Min + halfy + halfz, ContainerBox.Max - halfx)));
            this.ChildLeaves.Add(new OctreeLeaf(new BoundingBox(ContainerBox.Min + half, ContainerBox.Max)));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="depth"></param>
        public void Distribute(int depth)
        {
            if (this.ContainedObjects.Count > MaxObjects && depth <= MaxDepth)
            {
                Split();
                for (int i = this.ContainedObjects.Count - 1; i >= 0; i--)// (OctreeObject item in containedObjects)
                {
                    OctreeObject item = this.ContainedObjects[i];
                    foreach (OctreeLeaf leaf in ChildLeaves)
                    {
                        if (leaf.ContainerBox.Contains(item.Bounds) == ContainmentType.Contains)
                        {
                            leaf.ContainedObjects.Add(item);
                            this.ContainedObjects.Remove(item);
                            break;
                        }
                    }
                }

                depth++;
                foreach (OctreeLeaf leaf in ChildLeaves)
                    leaf.Distribute(depth);
                depth--;
            }
        }
        /// <summary>
        /// Fast Add Childern, No Checking
        /// </summary>
        /// <param name="objects">Destination List</param>
        protected void FastAddChildren(List<OctreeObject> objects)
        {
            foreach (OctreeObject item in this.ContainedObjects)
                objects.Add(item);

            if (ChildLeaves != null)
            {
                foreach (OctreeLeaf leaf in ChildLeaves)
                    leaf.FastAddChildren(objects);
            }
        }
        /// <summary>
        /// Returns a list of objects in bounding Frustum
        /// </summary>
        /// <param name="objects">Destination List</param>
        /// <param name="boundingFrustum">Frustum to View</param>
        public virtual void ObjectsInFrustum(List<OctreeObject> objects, BoundingFrustum boundingFrustum)
        {
            // if the current box is totally contained in our leaf, then add me and all my kids
            if (DoFastOut && boundingFrustum.Contains(ContainerBox) == ContainmentType.Contains)
                FastAddChildren(objects);
            else
            {
                // ok so we know that we are probably intersecting or outside
                foreach (OctreeObject item in this.ContainedObjects) // add our straglers
                    objects.Add(item);

                if (ChildLeaves != null)
                {
                    foreach (OctreeLeaf leaf in ChildLeaves)
                    {
                        // if the child is totally in the volume then add it and it's kids
                        if (DoFastOut && boundingFrustum.Contains(leaf.ContainerBox) == ContainmentType.Contains)
                            leaf.FastAddChildren(objects);
                        else
                        {
                            if (boundingFrustum.Intersects(leaf.ContainerBox))
                                leaf.ObjectsInFrustum(objects, boundingFrustum);
                        }

                    }
                }
            }
        }
        /// <summary>
        /// Returns a list of objects in bounding box
        /// </summary>
        /// <param name="objects">Destination List</param>
        /// <param name="boundingBox">Frustum to View</param>
        public virtual void ObjectsInBoundingBox(List<OctreeObject> objects, BoundingBox boundingBox)
        {
            // if the current box is totally contained in our leaf, then add me and all my kids
            if (boundingBox.Contains(ContainerBox) == ContainmentType.Contains)
                FastAddChildren(objects);
            else
            {
                // ok so we know that we are probably intersecting or outside
                foreach (OctreeObject item in this.ContainedObjects) // add our straglers
                    objects.Add(item);

                if (ChildLeaves != null)
                {
                    foreach (OctreeLeaf leaf in ChildLeaves)
                    {
                        // see if any of the sub boxes intesect our frustum
                        if (leaf.ContainerBox.Intersects(boundingBox))
                            leaf.ObjectsInBoundingBox(objects, boundingBox);
                    }
                }
            }
        }
        /// <summary>
        /// Returns a list of objects in bounding box
        /// </summary>
        /// <param name="objects">Destination List</param>
        /// <param name="boundingSphere">Frustum to View</param>
        public virtual void ObjectsInBoundingSphere(List<OctreeObject> objects, BoundingSphere boundingSphere)
        {
            // if the current box is totally contained in our leaf, then add me and all my kids
            if (boundingSphere.Contains(ContainerBox) == ContainmentType.Contains)
                FastAddChildren(objects);
            else
            {
                // ok so we know that we are probably intersecting or outside
                foreach (OctreeObject item in this.ContainedObjects) // add our straglers
                    objects.Add(item);

                if (this.ChildLeaves != null)
                {
                    foreach (OctreeLeaf leaf in ChildLeaves)
                    {
                        // see if any of the sub boxes intesect our frustum
                        if (leaf.ContainerBox.Intersects(boundingSphere))
                            leaf.ObjectsInBoundingSphere(objects, boundingSphere);
                    }
                }
            }
        }
        /// <summary>
        /// Test if bounding box in in frustum
        /// </summary>
        protected ContainmentType testBoxInFrustum(BoundingBox extents, BoundingFrustum frustum)
        {
            // TODO - use a sphere vs. cone test first?

            Vector3 inside;  // inside point  (assuming partial)
            Vector3 outside; // outside point (assuming partial)
            float len = 0;
            ContainmentType result = ContainmentType.Contains;

            foreach (Plane plane in frustum.Planes)
            {
                // setup the inside/outside corners
                // this can be determined easily based
                // on the normal vector for the plane
                if (plane.Normal.X > 0.0f)
                {
                    inside.X = extents.Max.X;
                    outside.X = extents.Min.X;
                }
                else
                {
                    inside.X = extents.Min.X;
                    outside.X = extents.Max.X;
                }

                if (plane.Normal.Y > 0.0f)
                {
                    inside.Y = extents.Max.Y;
                    outside.Y = extents.Min.Y;
                }
                else
                {
                    inside.Y = extents.Min.Y;
                    outside.Y = extents.Max.Y;
                }

                if (plane.Normal.Z > 0.0f)
                {
                    inside.Z = extents.Max.Z;
                    outside.Z = extents.Min.Z;
                }
                else
                {
                    inside.Z = extents.Min.Z;
                    outside.Z = extents.Max.Z;
                }

                // check the inside length
                len = plane.Distance(inside);
                if (len < -1.0f)
                    return ContainmentType.Disjoint; // box is fully outside the frustum

                // check the outside length
                len = plane.Distance(outside);
                if (len < -1.0f)
                    result = ContainmentType.Intersects; // partial containment at best
            }

            return result;
        }
        #endregion
        //--------------------------------------------------------//
    }
    //------------------------------------------------------------//
}
